#ifndef __LDX_SVIEWFRUSTUM_H_INCLUDE__
#define __LDX_SVIEWFRUSTUM_H_INCLUDE__
#include "LDXConfig.h"
#include "CRenderDriverD3D11.h"

namespace ldx
{
	namespace scene
	{

		//! Defines the view frustum. That's the space visible by the camera.
		/** The view frustum is enclosed by 6 planes. These six planes share
		eight points. A bounding box around these eight points is also stored in
		this structure.
		*/
		struct SViewFrustum
		{
			enum VFPLANES
			{
				//! Far plane of the frustum. That is the plane farest away from the eye.
				VF_FAR_PLANE = 0,
				//! Near plane of the frustum. That is the plane nearest to the eye.
				VF_NEAR_PLANE,
				//! Left plane of the frustum.
				VF_LEFT_PLANE,
				//! Right plane of the frustum.
				VF_RIGHT_PLANE,
				//! Bottom plane of the frustum.
				VF_BOTTOM_PLANE,
				//! Top plane of the frustum.
				VF_TOP_PLANE,

				//! Amount of planes enclosing the view frustum. Should be 6.
				VF_PLANE_COUNT
			};
			
			//! Default Constructor
			SViewFrustum() {}

			//! Copy Constructor
			SViewFrustum(const SViewFrustum& other);

			//! This constructor creates a view frustum based on a projection and/or view matrix.
			SViewFrustum(const mt::mat4& mat);

			//! This constructor creates a view frustum based on a projection and/or view matrix.
			inline void setFrom(const mt::mat4& mat);

			//! transforms the frustum by the matrix
			/** \param mat: Matrix by which the view frustum is transformed.*/
			void transform(const mt::mat4& mat);

			//! returns the point which is on the far left upper corner inside the the view frustum.
			mt::vec3f getFarLeftUp() const;

			//! returns the point which is on the far left bottom corner inside the the view frustum.
			mt::vec3f getFarLeftDown() const;

			//! returns the point which is on the far right top corner inside the the view frustum.
			mt::vec3f getFarRightUp() const;

			//! returns the point which is on the far right bottom corner inside the the view frustum.
			mt::vec3f getFarRightDown() const;

			//! returns the point which is on the near left upper corner inside the the view frustum.
			mt::vec3f getNearLeftUp() const;

			//! returns the point which is on the near left bottom corner inside the the view frustum.
			mt::vec3f getNearLeftDown() const;

			//! returns the point which is on the near right top corner inside the the view frustum.
			mt::vec3f getNearRightUp() const;

			//! returns the point which is on the near right bottom corner inside the the view frustum.
			mt::vec3f getNearRightDown() const;

			//! returns a bounding box enclosing the whole view frustum
			const mt::aabbox3df &getBoundingBox() const;

			//! recalculates the bounding box member based on the planes
			inline void recalculateBoundingBox();

			//! get the given state's matrix based on frustum E_TRANSFORMATION_STATE
			mt::mat4& getTransform(render::E_TRANFORM_MATRIX state);

			//! get the given state's matrix based on frustum E_TRANSFORMATION_STATE
			const mt::mat4& getTransform(render::E_TRANFORM_MATRIX state) const;

			//! clips a line to the view frustum.
			/** \return True if the line was clipped, false if not */
			bool clipLine(mt::line3df& line) const;

			//! the position of the camera
			mt::vec3f cameraPosition;

			//! all planes enclosing the view frustum.
			mt::plane planes[VF_PLANE_COUNT];

			//! bounding box around the view frustum
			mt::aabbox3df boundingBox;

		private:
			//! Hold a copy of important transform matrices
			enum E_TRANSFORMATION_STATE_FRUSTUM
			{
				ETS_VIEW = 0,
				ETS_PROJECTION = 1,
				ETS_CAMERA = 2,
				ETS_COUNT_FRUSTUM
			};

			//! Hold a copy of important transform matrices
			mt::mat4 Matrices[ETS_COUNT_FRUSTUM];
		};

		inline ldx::scene::SViewFrustum::SViewFrustum(const SViewFrustum & other)
		{
			cameraPosition = other.cameraPosition;

			for (uint i = 0; i < VF_PLANE_COUNT; ++i)
				planes[i] = other.planes[i];
			boundingBox = other.boundingBox;

			for (uint i = 0; i < ETS_COUNT_FRUSTUM; ++i)
				Matrices[i] = other.Matrices[i];
		}

		inline ldx::scene::SViewFrustum::SViewFrustum(const mt::mat4 & mat)
		{
			setFrom(mat);
		}

		inline void SViewFrustum::setFrom(const mt::mat4& mat)
		{
			// left clipping plane
			planes[VF_LEFT_PLANE].SND.Normal.X = mat[3] + mat[0];
			planes[VF_LEFT_PLANE].SND.Normal.Y = mat[7] + mat[4];
			planes[VF_LEFT_PLANE].SND.Normal.Z = mat[11] + mat[8];
			planes[VF_LEFT_PLANE].SND.D = mat[15] + mat[12];

			// right clipping plane
			planes[VF_RIGHT_PLANE].SND.Normal.X = mat[3] - mat[0];
			planes[VF_RIGHT_PLANE].SND.Normal.Y = mat[7] - mat[4];
			planes[VF_RIGHT_PLANE].SND.Normal.Z = mat[11] - mat[8];
			planes[VF_RIGHT_PLANE].SND.D = mat[15] - mat[12];

			// top clipping plane
			planes[VF_TOP_PLANE].SND.Normal.X = mat[3] - mat[1];
			planes[VF_TOP_PLANE].SND.Normal.Y = mat[7] - mat[5];
			planes[VF_TOP_PLANE].SND.Normal.Z = mat[11] - mat[9];
			planes[VF_TOP_PLANE].SND.D = mat[15] - mat[13];

			// bottom clipping plane
			planes[VF_BOTTOM_PLANE].SND.Normal.X = mat[3] + mat[1];
			planes[VF_BOTTOM_PLANE].SND.Normal.Y = mat[7] + mat[5];
			planes[VF_BOTTOM_PLANE].SND.Normal.Z = mat[11] + mat[9];
			planes[VF_BOTTOM_PLANE].SND.D = mat[15] + mat[13];

			// far clipping plane
			planes[VF_FAR_PLANE].SND.Normal.X = mat[3] - mat[2];
			planes[VF_FAR_PLANE].SND.Normal.Y = mat[7] - mat[6];
			planes[VF_FAR_PLANE].SND.Normal.Z = mat[11] - mat[10];
			planes[VF_FAR_PLANE].SND.D = mat[15] - mat[14];

			// near clipping plane
			planes[VF_NEAR_PLANE].SND.Normal.X = mat[2];
			planes[VF_NEAR_PLANE].SND.Normal.Y = mat[6];
			planes[VF_NEAR_PLANE].SND.Normal.Z = mat[10];
			planes[VF_NEAR_PLANE].SND.D = mat[14];

			// normalize normals
			uint i;
			for (i = 0; i != VF_PLANE_COUNT; ++i)
			{
				const float len = -mt::reciprocal_squareroot(
				planes[i].SND.Normal.getLengthSQ());
				planes[i].SND.Normal *= len;
				planes[i].SND.D *= len;
			}

			// make bounding box
			recalculateBoundingBox();
		}

		inline void ldx::scene::SViewFrustum::transform(const mt::mat4 & mat)
		{
			for (uint i = 0; i<VF_PLANE_COUNT; ++i)
				mat.transformPlane(planes[i]);

			mat.transformVec(cameraPosition);
			recalculateBoundingBox();
		}

		inline mt::vec3f SViewFrustum::getFarLeftUp() const
		{
			mt::vec3f p;
			planes[scene::SViewFrustum::VF_FAR_PLANE].getIntersectionWithPlanes(
				planes[scene::SViewFrustum::VF_TOP_PLANE],
				planes[scene::SViewFrustum::VF_LEFT_PLANE], p);

			return p;
		}

		inline mt::vec3f SViewFrustum::getFarLeftDown() const
		{
			mt::vec3f p;
			planes[scene::SViewFrustum::VF_FAR_PLANE].getIntersectionWithPlanes(
				planes[scene::SViewFrustum::VF_BOTTOM_PLANE],
				planes[scene::SViewFrustum::VF_LEFT_PLANE], p);

			return p;
		}

		inline mt::vec3f SViewFrustum::getFarRightUp() const
		{
			mt::vec3f p;
			planes[scene::SViewFrustum::VF_FAR_PLANE].getIntersectionWithPlanes(
				planes[scene::SViewFrustum::VF_TOP_PLANE],
				planes[scene::SViewFrustum::VF_RIGHT_PLANE], p);

			return p;
		}

		inline mt::vec3f SViewFrustum::getFarRightDown() const
		{
			mt::vec3f p;
			planes[scene::SViewFrustum::VF_FAR_PLANE].getIntersectionWithPlanes(
				planes[scene::SViewFrustum::VF_BOTTOM_PLANE],
				planes[scene::SViewFrustum::VF_RIGHT_PLANE], p);

			return p;
		}

		inline mt::vec3f SViewFrustum::getNearLeftUp() const
		{
			mt::vec3f p;
			planes[scene::SViewFrustum::VF_NEAR_PLANE].getIntersectionWithPlanes(
				planes[scene::SViewFrustum::VF_TOP_PLANE],
				planes[scene::SViewFrustum::VF_LEFT_PLANE], p);

			return p;
		}

		inline mt::vec3f SViewFrustum::getNearLeftDown() const
		{
			mt::vec3f p;
			planes[scene::SViewFrustum::VF_NEAR_PLANE].getIntersectionWithPlanes(
				planes[scene::SViewFrustum::VF_BOTTOM_PLANE],
				planes[scene::SViewFrustum::VF_LEFT_PLANE], p);

			return p;
		}

		inline mt::vec3f SViewFrustum::getNearRightUp() const
		{
			mt::vec3f p;
			planes[scene::SViewFrustum::VF_NEAR_PLANE].getIntersectionWithPlanes(
				planes[scene::SViewFrustum::VF_TOP_PLANE],
				planes[scene::SViewFrustum::VF_RIGHT_PLANE], p);

			return p;
		}

		inline mt::vec3f SViewFrustum::getNearRightDown() const
		{
			mt::vec3f p;
			planes[scene::SViewFrustum::VF_NEAR_PLANE].getIntersectionWithPlanes(
				planes[scene::SViewFrustum::VF_BOTTOM_PLANE],
				planes[scene::SViewFrustum::VF_RIGHT_PLANE], p);

			return p;
		}

		inline const mt::aabbox3df & SViewFrustum::getBoundingBox() const
		{
			return boundingBox;
		}

		inline void SViewFrustum::recalculateBoundingBox()
		{
			boundingBox.reset(cameraPosition);

			boundingBox.addInternalPoint(getFarLeftUp());
			boundingBox.addInternalPoint(getFarRightUp());
			boundingBox.addInternalPoint(getFarLeftDown());
			boundingBox.addInternalPoint(getFarRightDown());
		}

		inline mt::mat4 & SViewFrustum::getTransform(render::E_TRANFORM_MATRIX state)
		{
			uint index = 0;
			switch (state)
			{
			case render::ETM_VIEW:
				index = SViewFrustum::ETS_VIEW; break;
			case render::ETM_PROJECTION:
				index = SViewFrustum::ETS_PROJECTION; break;
			case render::ETM_CAMERA:
				index = SViewFrustum::ETS_CAMERA; break;
			default:
				break;
			}
			return Matrices[index];
		}

		inline const mt::mat4 & SViewFrustum::getTransform(render::E_TRANFORM_MATRIX state) const
		{
			uint index = 0;
			switch (state)
			{
			case render::ETM_VIEW:
				index = SViewFrustum::ETS_VIEW; break;
			case render::ETM_PROJECTION:
				index = SViewFrustum::ETS_PROJECTION; break;
			case render::ETM_CAMERA:
				index = SViewFrustum::ETS_CAMERA; break;
			default:
				break;
			}
			return Matrices[index];
		}

		inline bool SViewFrustum::clipLine(mt::line3df & line) const
		{
			bool wasClipped = false;
			for (uint i = 0; i < VF_PLANE_COUNT; ++i)
			{
				if (planes[i].classifyPointRelation(line.start) == mt::ISREL3D_FRONT)
				{
					line.start = line.start.getInterpolated(line.end,
						planes[i].getKnownIntersectionWithLine(line.start, line.end));
					wasClipped = true;
				}
				if (planes[i].classifyPointRelation(line.end) == mt::ISREL3D_FRONT)
				{
					line.end = line.start.getInterpolated(line.end,
						planes[i].getKnownIntersectionWithLine(line.start, line.end));
					wasClipped = true;
				}
			}
			return wasClipped;
		}


	}
}

#endif//! __LDX_SVIEWFRUSTUM_H_INCLUDE__